////////////////////////////////////////////////////////////////////////////
//
//  CryEngine Source File.
//  Copyright (C), Crytek.
// -------------------------------------------------------------------------
//  File name:   FileReadAheadCache.cpp
//  Created:     18/08/2010 by Timur.
//  Description: Read Ached cache for file operations.
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include <StdAfx.h>
#include "FileReadAheadCache.h"

#include "StreamEngine.h"
#include "../System.h"
#include "../CustomMemoryHeap.h"

#define READ_AHEAD_THREAD_NAME "FileReadAheadCache"
#define READ_AHEAD_MAX_FILE_SIZE (4*1024*1024)

namespace
{
	//////////////////////////////////////////////////////////////////////////
	inline bool CheckIfFilenameCanBeReadAhead( const char *sFilename )
	{
		// discard certain file extensions.
		const char *sExt = PathUtil::GetExt(sFilename);
		if (0 == strcmp(sExt,"fxcb"))
			return false;
		if (0 == strcmp(sExt,"cfxb"))
			return false;
		if (0 == strcmp(sExt,"tif"))
			return false;

#ifndef WIN32
		//if (0 == strcmp(sExt,"cgf"))
			//return false;
#endif












		return true;
	}
}


//////////////////////////////////////////////////////////////////////////
CFileReadAheadWorkerThread::CFileReadAheadWorkerThread( CFileReadAheadCache *pCache )
{
	m_pCache = pCache;
	m_bCancelThreadRequest = false;
	m_nCurrentFile = 0;

	m_pStreamEngine = (CStreamEngine*)GetISystem()->GetStreamEngine();

	if (g_cvars.pakVars.nReadAheadCache != 0)
	{
		// Starts thread
		Start( (unsigned)1 << g_cvars.sys_streaming_cpu,READ_AHEAD_THREAD_NAME );
	}
}

CFileReadAheadWorkerThread::~CFileReadAheadWorkerThread()
{
	Cancel();
	WaitForThread();
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadWorkerThread::Run()
{
	SetName( READ_AHEAD_THREAD_NAME );









	// Main thread loop
	while (!m_bCancelThreadRequest)
	{
		m_awakeEvent.Wait();
		m_awakeEvent.Reset();

			while (!m_bCancelThreadRequest && m_nCurrentFile < (int)m_filesSequence.size())
		{
			const char *sFilename = m_filesSequence[m_nCurrentFile++];
			if (sFilename && CheckIfFilenameCanBeReadAhead( sFilename ))
			{
				// Create file request for streaming engine.
				CAsyncIOFileRequest *pFileRequest = new CAsyncIOFileRequest(eStreamTaskTypeReadAhead);
				pFileRequest->m_strFileName = sFilename;
				pFileRequest->m_ePriority = estpUrgent;
				pFileRequest->m_pCallback = m_pCache;
				pFileRequest->m_maxFileSize = READ_AHEAD_MAX_FILE_SIZE;
				pFileRequest->m_bNoSorting = true;
				
				// Submit file request.
				m_pStreamEngine->StartFileRequest(pFileRequest);
			}
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadWorkerThread::Cancel()
{
	m_bCancelThreadRequest = true;
	m_awakeEvent.Set();
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadWorkerThread::StartLoading( const FileReadAheadRequestSequence &sequence )
{
	m_filesSequence = sequence; // Copy vector

	m_nCurrentFile = 0;
	m_awakeEvent.Set();
	
	// Give opportunity for the pre loading thread to start loading files.
	CrySleep(100);
}

//////////////////////////////////////////////////////////////////////////
CFileReadAheadCache::CFileReadAheadCache()
{
	m_nCounter = 0;
	m_bReadAheadEnabled = false;

	m_crc32gen.Init();

	// Initialize array of opened files.
	m_openedFiles.resize( MAX_OPENED_READ_AHEAD_FILES+1 );
	for (size_t i = 0,num = m_openedFiles.size(); i < num; i++)
	{
		SOpenedFile *pFile = &m_openedFiles[i];
		pFile->m_file = (FileHandle)i;
		pFile->m_nCursor = 0;
		pFile->m_nFileSize = 0;
	}
	m_pMemoryHeap = new CCustomMemoryHeap(false);
}

//////////////////////////////////////////////////////////////////////////
CFileReadAheadCache::~CFileReadAheadCache()
{
	m_pMemoryHeap = 0;
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::StartLoadingResourceList( const FileReadAheadRequestSequence &sequence )
{
	m_bReadAheadEnabled = true;
	if (!m_workerThread)
	{
		m_workerThread = new CFileReadAheadWorkerThread(this);
		m_workerThread->StartLoading( sequence );
	}
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::StopLoadingResourceList()
{
	m_bReadAheadEnabled = false;
	if (m_workerThread)
	{
		m_workerThread->Cancel();
		m_workerThread->WaitForThread();
		m_workerThread = 0;
	}
	ClearCache();
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::OnAsyncFinished( CAsyncIOFileRequest *pFileRequest )
{
	AddCacheEntry(pFileRequest);
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::AddCacheEntry( CAsyncIOFileRequest *pFileRequest )
{
	if (!m_bReadAheadEnabled)
		return;

	if (pFileRequest->m_pMemoryBuffer && pFileRequest->m_nErrorCode == 0 && pFileRequest->IsCanceled() /*&& pFileRequest->m_eMediaType != eStreamSourceTypeMemory*/)
	{
		uint32 nCRC32 = m_crc32gen.GetCRC32Lowercase( pFileRequest->m_strFileName.c_str() );

		_smart_ptr<SCacheEntry> pNewCacheEntry = GetNewCacheEntry();
		pNewCacheEntry->m_sFilename = pFileRequest->m_strFileName;
		pNewCacheEntry->m_nCRC32 = nCRC32;
		
		// Running counter
		pNewCacheEntry->m_nCounter = ++m_nCounter;

		size_t nSize = pFileRequest->m_pMemoryBuffer->GetSize();
		pNewCacheEntry->m_pMemoryBlock = m_pMemoryHeap->AllocateBlock( nSize,"ReadAhead Cache" );
		memcpy( pNewCacheEntry->m_pMemoryBlock->GetData(),pFileRequest->m_pMemoryBuffer->GetData(),nSize );
		pFileRequest->FreeBuffer(); // We do not need temp memory buffer from file request anymore.

		{
			CryAutoCriticalSection lock(m_cacheLock);
			if (m_cacheMap.find(nCRC32) == m_cacheMap.end())
			{
				m_cacheMap.insert( CacheMap::value_type(nCRC32,pNewCacheEntry) );
			}
		}

		//{ string str;str.Format("[Add] %s\n",pFileRequest->m_strFileName.c_str()); OutputDebugString( str.c_str() ); }
		//CryLog( "[Add] %s",pFileRequest->m_strFileName.c_str() );
	}
	else
	{
		pFileRequest->FreeBuffer(); // We do not need temp memory buffer from file request anymore.
	}
}

//////////////////////////////////////////////////////////////////////////
CFileReadAheadCache::SCacheEntry* CFileReadAheadCache::GetNewCacheEntry()
{
	return new SCacheEntry;
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::FreeCacheEntry( SCacheEntry *pCacheEntry )
{
	CryAutoCriticalSection lock(m_cacheLock);
	m_cacheMap.erase( pCacheEntry->m_nCRC32 );
}


//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::ClearCache()
{
	CryAutoCriticalSection lock(m_cacheLock);
	m_nCounter = 0;
	m_cacheMap.clear();
}

//////////////////////////////////////////////////////////////////////////
CFileReadAheadCache::SOpenedFile* CFileReadAheadCache::GetOpenedFile( FILE* file )
{
	FileHandle fh = ToFileHandle(file);
	if (fh)
	{
		CryAutoCriticalSection lock(m_openedFilesLock);

		size_t nIndex = (size_t)fh;
		if (nIndex > 0 && nIndex < m_openedFiles.size())
			return &m_openedFiles[nIndex];
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
FILE* CFileReadAheadCache::FOpen( const char *filename )
{
	if (!m_bReadAheadEnabled)
		return 0;

	if (!CheckIfFilenameCanBeReadAhead(filename))
		return 0;

	uint32 nCRC32 = m_crc32gen.GetCRC32Lowercase( filename );
	_smart_ptr<SCacheEntry> pCacheEntry = 0;
	
	{
		CryAutoCriticalSection lock(m_cacheLock);
		pCacheEntry = stl::find_in_map( m_cacheMap,nCRC32,0 );
	}

	if (pCacheEntry)
	{
#ifdef _DEBUG
		// Sanity check, filename must match
		if (stricmp(filename,pCacheEntry->m_sFilename.c_str()) != 0)
		{
			return 0;
		}
#endif

		CryAutoCriticalSection lock(m_openedFilesLock);

		SOpenedFile *pFile = 0;
		for (size_t i = 1,num = m_openedFiles.size(); i < num; i++) // We start from 1, first slot is unused
		{
			if (!m_openedFiles[i].m_pCacheEntry)
			{
				pFile = &m_openedFiles[i];
				break;
			}
		}
		if (pFile)
		{
			//CryLog( "[ReadAheadCache] %s",filename );
			pFile->m_pCacheEntry = pCacheEntry;
			pFile->m_nCursor = 0;
			pFile->m_nFileSize = pFile->m_pCacheEntry->m_pMemoryBlock->GetSize();
			return FromFileHandle( pFile->m_file );
		}
	}
	else
	{
		/*
		// File missed the cache.
		if (gEnv->pCryPak->IsFileExist(filename,ICryPak::eFileLocation_InPak))
		{
			CryLog( "[ReadAheadCache Miss] %s",filename );
		}
		*/
	}

	return 0;
}

//////////////////////////////////////////////////////////////////////////
int CFileReadAheadCache::FSeek( FILE* file,int64 origin,int command )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		int retCode = -1;
		int64 newPos;
		switch (command) 
		{
		case SEEK_SET:
			newPos = origin;
			if (newPos >= 0 && newPos <= pFile->m_nFileSize)
			{
				pFile->m_nCursor = newPos;
				retCode = 0;
			}
			break;
		case SEEK_CUR:
			newPos = origin + pFile->m_nCursor;
			if (newPos >= 0 && newPos <= pFile->m_nFileSize)
			{
				pFile->m_nCursor =  newPos;
				retCode = 0;
			}
			break;
		case SEEK_END:
			newPos = pFile->m_nFileSize - origin;
			if (newPos >= 0 && newPos <= pFile->m_nFileSize)
			{
				pFile->m_nCursor =  newPos;
				retCode = 0;
			}
			break;
		default:
			// Not valid disk operation!
			assert(0);
		}
		return retCode;
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
size_t CFileReadAheadCache::FRead( FILE* file,void *data, size_t nElementSize, size_t nCount )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		int64 nRead = (int64)nCount * (int64)nElementSize;
		int64 nCanBeRead = pFile->m_nFileSize - pFile->m_nCursor;
		if (nRead > nCanBeRead)
			nRead = nCanBeRead;

		pFile->m_pCacheEntry->m_pMemoryBlock->CopyMemoryRegion( data,(size_t)pFile->m_nCursor,(size_t)nRead );
		pFile->m_nCursor += nRead;
		return (size_t)(nRead / nElementSize);
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
int64 CFileReadAheadCache::FTell( FILE* file )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		return pFile->m_nCursor;
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
size_t CFileReadAheadCache::FGetSize( FILE* file )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		return (size_t)pFile->m_nFileSize;
	}
	return 0;
}

//////////////////////////////////////////////////////////////////////////
void* CFileReadAheadCache::FGetCachedFileData( FILE *file,size_t &nFileSize )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		nFileSize = (size_t)pFile->m_nFileSize;
		pFile->m_pCachedMemory = gEnv->pCryPak->PoolAllocMemoryBlock(nFileSize,"FGetCachedFileData for ReadAheadCache");
		void *pOutputData = pFile->m_pCachedMemory->GetData();
		pFile->m_pCacheEntry->m_pMemoryBlock->CopyMemoryRegion( pOutputData,0,nFileSize );
		return pOutputData;
	}
	nFileSize = 0;
	return 0;
}

//////////////////////////////////////////////////////////////////////////
void CFileReadAheadCache::FClose( FILE* file )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		FreeCacheEntry( pFile->m_pCacheEntry );
		{
			CryAutoCriticalSection lock(m_openedFilesLock);
			pFile->m_pCacheEntry = 0;
			pFile->m_nCursor = 0;
			pFile->m_nFileSize = 0;
			pFile->m_pCachedMemory = 0;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
int CFileReadAheadCache::FEof( FILE* file )
{
	SOpenedFile *pFile = GetOpenedFile(file);
	if (pFile)
	{
		return (unsigned int)(pFile->m_nCursor >= pFile->m_nFileSize);
	}
	return 0;
}
